import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt

plt.switch_backend('TkAgg')

# %matplotlib inline

# 导入数据集
iris = pd.read_csv('iris.txt', header=None)
print('iris:\n', iris[:3])
# print(len(iris.columns))
# iris = iris.iloc[:, :len(iris.columns) - 1]
# print('iris:\n', iris[:3])


# 计算距离
def distEclud(arrA, arrB):
    # d = a-b
    d = arrA - arrB
    # sum(d^2)
    dist = np.sum(np.power(d, 2), axis=1)
    return dist


# 自动生成随机质心
def randCent(dataSet, k):
    n = dataSet.shape[1]
    # 取每一列的最大最小值
    data_max = dataSet.iloc[:,:n-1].max()
    data_min = dataSet.iloc[:,:n-1].min()
    # 均匀分布抽样
    # numpy.random.uniform(low,high,size)
    # 从一个均匀分布[low,high)中随机采样，注意定义域是左闭右开
    # size: 输出样本数目，为int或元组(tuple)类型，
    # 例如，size=(m,n,k), 则输出 m * n * k 个样本，缺省时输出1个值。
    data_cent = np.random.uniform(data_min, data_max, (k, n-1))
    return data_cent  # 质心


# 随机生成3个质心
iris_cent = randCent(iris, 3)


def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):  # iris为150*5
    m, n = dataSet.shape  # m是行数（数据量），n是列数iris为150*5
    # 下面生成的centroids，即第一个容器，后面用来存储最新更新的质心
    centroids = createCent(dataSet, k)  # centroids为3*4，用三个长度为4的一维数组记载3个质心
    # 第一次centroids是随机生成的
    # 这段生成的result_set，即第二个容器
    # result_set结构： [数据集, 该行到最近质心的距离， 本次迭代中最近质心编号，上次迭代中最近质心编号]
    clusterAssment = np.zeros((m, 3))  # clusterAssment为150*3的数组
    clusterAssment[:, 0] = np.inf  # np.inf为无穷大
    clusterAssment[:, 1: 3] = -1  # 此时clusterAssment为150*3
    result_set = pd.concat([dataSet, pd.DataFrame(clusterAssment)],
                           axis=1, ignore_index=True)  # result_set为150*8

    clusterChanged = True
    while clusterChanged:
        clusterChanged = False

        for i in range(m):  # 遍历result_set中每一行，一共m行
            # 小心，下面的n为5，而resulit_set的列数已经变成8
            dist = distMeas(dataSet.iloc[i, :n-1].values , centroids)  # 第i行与三个质心的距离,dist为3*1
            result_set.iloc[i, n] = dist.min()  # result_set[i,n]记录该行与3个质心的最小距离
            result_set.iloc[i, n + 1] = np.where(dist == dist.min())[0]  # result_set[i,n]记录最近质心的索引
            clusterChanged = not (result_set.iloc[:, -1] == result_set.iloc[:, -2]).all()
            # 只要result_set最后两列不完全相等，意味着本次for循环结束时，m行所有的新质心与上次while循环留下的不完全一样
        # 后果：clusterChanged为True，while继续循环

        # clusterChanged为True，则需要运行下面的if语句代码块，重置第一个容器centroids和第二个容器result_set
        if clusterChanged:
            cent_df = result_set.groupby(n + 1).mean()  # 按照列索引为n+1（质心索引）(第6列)进行分组求均值
            # 即：按照最新的簇分类，计算最新3个质心的位置
            centroids = cent_df.iloc[:, :n - 1].values  # 重置centroids，用最新质心位置，替换上次的。3*4
            result_set.iloc[:, -1] = result_set.iloc[:, -2]  # result_set最后一列，本次的簇分类编码，替换掉上次的
    return centroids, result_set


iris_cent, iris_result = kMeans(iris, 3)
print(iris_result.head())
